#include "orangepch.h"
#include "OpenGLShader.h"
#include "Core/Core.h"
#include "Core/Log.h"
#include "glad/glad.h"
#include "glm/gtc/type_ptr.hpp"
#include <fstream>
namespace Orange {

	static GLenum ShaderTypeFromGLenum(std::string& type) {
		if (type == "vertex")
			return GL_VERTEX_SHADER;
		if (type == "fragment" || type == "pixel")
			return GL_FRAGMENT_SHADER;

		ORANGE_CORE_ERROR("Unknow Shader Type");
		return 0;
		
	}

	OpenGLShader::OpenGLShader(const std::string& filepath) {
		const std::string source = ReadFile(filepath);
		std::unordered_map<GLenum, std::string> shaderSources = PreProcess(source);
		Compile(shaderSources);

		size_t lastSlash = filepath.find_last_of("/\\");
		lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;
		size_t lastDot = filepath.rfind(".");
		size_t count = lastDot == std::string::npos ? filepath.size() - lastSlash : lastDot - lastSlash;

		m_Name = filepath.substr(lastSlash, count);
		
	};
	OpenGLShader::OpenGLShader(const std::string& name, const std::string& vertexShaderSrc, const std::string& fragmentShaderSrc):m_Name(name)
	{
		std::unordered_map<GLenum, std::string> shaderSources;
		shaderSources[GL_VERTEX_SHADER] = vertexShaderSrc;
		shaderSources[GL_FRAGMENT_SHADER] = fragmentShaderSrc;
		Compile(shaderSources);
	
	}
	OpenGLShader::~OpenGLShader()
	{
		glDeleteProgram(m_RendererId);
	}
	void OpenGLShader::bind()
	{
		glUseProgram(m_RendererId);
	}
	void OpenGLShader::ubind()
	{
		glUseProgram(0);
	}
	void OpenGLShader::SetMat4(const std::string& name, const glm::mat4& mat)
	{
		UploadUniformMat4f(name, mat);
	}
	void OpenGLShader::SetFloat(const std::string& name, const float value)
	{
		UploadUniform1f(name, value);
	}
	void OpenGLShader::SetFloat4(const std::string& name, const glm::vec4& mat)
	{
		UploadUniform4f(name, mat);
	}
	void OpenGLShader::SetFloat3(const std::string& name, const glm::vec3& mat)
	{
		UploadUniform3f(name, mat);
	}
	void OpenGLShader::SetInt(const std::string& name, const int value)
	{
		UploadUniformInt(name, value);
	}
	void OpenGLShader::SetIntArray(const std::string& name,  int* value , uint32_t count)
	{
		UploadUniformIntArray(name, value, count);
	}
	
	void OpenGLShader::UploadUniformIntArray(const std::string& name, int* value, uint32_t count)
	{
		GLint location = glGetUniformLocation(m_RendererId, name.c_str());
		glUniform1iv(location, count, value);
	}
	void OpenGLShader::UploadUniformMat4f(const std::string& name, const glm::mat4& mat)
	{
		GLint location = glGetUniformLocation(m_RendererId, name.c_str());
		glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(mat));
	}
	void OpenGLShader::UploadUniform4f(const std::string& name, const glm::vec4& mat)
	{
		GLint location = glGetUniformLocation(m_RendererId, name.c_str());
		glUniform4f(location, mat.x, mat.y, mat.z, mat.w);
	}
	void OpenGLShader::UploadUniform3f(const std::string& name, const glm::vec3& mat)
	{
		GLint location = glGetUniformLocation(m_RendererId, name.c_str());
		glUniform3f(location, mat.x, mat.y, mat.z);
	}
	void OpenGLShader::UploadUniform2f(const std::string& name, const glm::vec2& mat)
	{
		GLint location = glGetUniformLocation(m_RendererId, name.c_str());
		glUniform2f(location, mat.x, mat.y);
	}
	void OpenGLShader::UploadUniform1f(const std::string& name, const float value)
	{
		GLint location = glGetUniformLocation(m_RendererId, name.c_str());
		glUniform1f(location, value);
	}
	void OpenGLShader::UploadUniformInt(const std::string& name, const int value)
	{
		GLint location = glGetUniformLocation(m_RendererId, name.c_str());
		glUniform1i(location, value);
	}



	std::string OpenGLShader::ReadFile(const std::string& filepath)
	{
		std::string result;
		std::ifstream in(filepath, std::ios::in | std::ios::binary);

		if (in) {
			/// <summary>
			/// ��¼�ļ�����λ��
			/// </summary>

			in.seekg(0, std::ios::end);
			result.resize(in.tellg());
			in.seekg(0, std::ios::beg);
			in.read(&result[0], result.size());

			in.close();
		}
		else {
			ORANGE_CORE_ASSERT(false, " could not open the file! ");
		}
		return result;
	}

	std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
	{
		 std::unordered_map<GLenum, std::string> shaderSources;
		 const char* typeToken = "#type";
		 size_t tokenLen = strlen(typeToken);
		 size_t pos = source.find(typeToken, 0);
		 while (pos != std::string::npos) {
			 size_t eol = source.find_first_of("\r\n", pos);
			 ORANGE_CORE_ASSERT(eol != std::string::npos, "Syntax Error!");
			 size_t begin = pos + tokenLen + 1;
			 std::string type = source.substr(begin, eol - begin);
			 
			 ORANGE_CORE_ASSERT(ShaderTypeFromGLenum(type), "Invalid shader type");

			 size_t nextLinePos = source.find_first_of("\r\n", eol);
			 pos = source.find(typeToken, nextLinePos);
			 shaderSources[ShaderTypeFromGLenum(type)] = source.substr(nextLinePos, pos -
				 (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));
		 }
		 return shaderSources;
	}

	void OpenGLShader::Compile(std::unordered_map<GLenum, std::string>& shaderSource)
	{

		GLenum program = glCreateProgram();
		std::array<GLenum,2> shaderIds;
		unsigned int shaderIdsIndex = 0;
		for (auto&kv : shaderSource) {

			GLenum type = kv.first;
			std::string shaderSrc = kv.second;
			// Create an empty vertex shader handle
			GLenum shader = glCreateShader(type);

			// Send the vertex shader source code to GL
			// Note that std::string's .c_str is NULL character terminated.
			const GLchar* source = shaderSrc.c_str();
			glShaderSource(shader, 1, &source, 0);

			// Compile the vertex shader
			glCompileShader(shader);

			GLint isCompiled = 0;
			glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
			if (isCompiled == GL_FALSE)
			{
				GLint maxLength = 0;
				glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);

				std::vector<GLchar> infoLog(maxLength);
				glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);


				glDeleteShader(shader);

				ORANGE_CORE_ERROR(infoLog.data());
				ORANGE_CORE_ASSERT(false, " shader compiled failure!");

				break;
			}
			glAttachShader(program, shader);
			shaderIds[shaderIdsIndex++]=(shader);
		}

	

		m_RendererId = program;
	

		glLinkProgram(program);


		GLint isLinked = 0;
		glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
		if (isLinked == GL_FALSE)
		{
			GLint maxLength = 0;
			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);

			// The maxLength includes the NULL character
			std::vector<GLchar> infoLog(maxLength);
			glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);

			// We don't need the program anymore.
			glDeleteProgram(program);
			// Don't leak shaders either.
			for (auto id : shaderIds) {
				glDeleteShader(id);
			}

			// Use the infoLog as you see fit.

			ORANGE_CORE_ERROR(infoLog.data());
			ORANGE_CORE_ASSERT(false, "link shader program failure!");

			// In this simple program, we'll just leave
			return;
		}
		for (auto id : shaderIds) {
			glDetachShader(m_RendererId, id);
		}

	}


	
}